package pjwstk.jps.interpreter.envs;

import java.util.*;

import pjwstk.jps.ast.*;
import pjwstk.jps.ast.auxname.*;
import pjwstk.jps.ast.binary.*;
import pjwstk.jps.ast.terminal.*;
import pjwstk.jps.ast.unary.*;
import pjwstk.jps.result.*;
import pjwstk.jps.datastore.*;
import pjwstk.jps.interpreter.qres.*;

public class Interpreter implements IInterpreter
{
	private ISBAStore store;
	private IQResStack qres;
	private IENVS envs;
	
	public Interpreter(ISBAStore aStore) {
		store = aStore;
	}
	
	@Override
	public void visitAsExpression(IAsExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		for (ISingleResult element : innerResult.getElements()) {
			IBinderResult binderRes = new BinderResult(expr.getAuxiliaryName(), element);
			this.envs.push(this.envs.nested(binderRes, store));
			result.getElements().add((binderRes));
		}
		qres.push(result);
	}

	@Override
	public void visitGroupAsExpression(IGroupAsExpression expr) {
		expr.getInnerExpression().accept(this);		
		IAbstractQueryResult innerResult = qres.pop();

		IBinderResult binderRes = new BinderResult(expr.getAuxiliaryName(), innerResult);
		this.envs.push(this.envs.nested(binderRes, store));
		qres.push(binderRes);
	}

	@Override
	public void visitAllExpression(IForAllExpression expr) {
		expr.getLeftExpression().accept(this);
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBooleanResult result = new BooleanResult(true); 
		for (ISingleResult leftElement : leftResult.getElements()) {
			IENVSFrame frame = envs.nested(leftElement, store);
			envs.push(frame);
			
			expr.getRightExpression().accept(this);
			ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
			expectBooleanResult(rightResult);
			
			envs.pop();
			
			if (!(Boolean) rightResult.getValue()) {
				result = (IBooleanResult) rightResult;
				break;
			}
		}
		qres.push(result);
	}
	
	@Override
    public void visitAndExpression(IAndExpression expr) {
		expr.getLeftExpression().accept(this);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        expectBooleanResult(leftResult);
        
        Boolean resBool = (Boolean)leftResult.getValue();
        if (resBool) {
            expr.getRightExpression().accept(this);
            ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
            expectBooleanResult(rightResult);
            
            resBool = (Boolean)rightResult.getValue();
        }
        
        qres.push(createSimpleResult(resBool));
    }
	
	@Override
	public void visitAnyExpression(IForAnyExpression expr) {
		expr.getLeftExpression().accept(this);
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBooleanResult result = new BooleanResult(false); 
		for (ISingleResult leftElement : leftResult.getElements()) {
			IENVSFrame frame = envs.nested(leftElement, store);
			envs.push(frame);
			
			expr.getRightExpression().accept(this);
			ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
			expectBooleanResult(rightResult);
			
			envs.pop();
			
			if ((Boolean) rightResult.getValue()) {
				result = (IBooleanResult) rightResult;
				break;
			}		
		}
		qres.push(result);
	}

	@Override
    public void visitCloseByExpression(ICloseByExpression expr) {
    }

	@Override
	public void visitCommaExpression(ICommaExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		IBagResult rightResult = toBagResult(qres.pop());
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = cartesianProduct(leftResult, rightResult);
		qres.push(result);
	}
	
	@Override
	public void visitNAndExpression(INAndExpression expr) {
		expr.getLeftExpression().accept(this);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        expectBooleanResult(leftResult);
        
        Boolean resBool = true;
        if ((Boolean)leftResult.getValue()) {

            expr.getRightExpression().accept(this);
            ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
            expectBooleanResult(rightResult);
            
            resBool = (Boolean)rightResult.getValue() ? false : true;
        }
        
        qres.push(createSimpleResult(resBool));
	}

	@Override
    public void visitDivideExpression(IDivideExpression expr) {
	    expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        
        ISimpleResult<?> result = null;
        
        if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
            Number resNum = divide((Number)leftResult.getValue(), (Number)rightResult.getValue());
            result = createSimpleResult(resNum);
        } else {
            throw new RuntimeException();
        }
        
        qres.push(result);
    }

	@Override
	public void visitDotExpression(IDotExpression expr) {
		expr.getLeftExpression().accept(this);
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		for (ISingleResult leftElement : leftResult.getElements()) {
			IENVSFrame frame = envs.nested(leftElement, store);
			envs.push(frame);
			
			expr.getRightExpression().accept(this);
			IBagResult rightResult = toBagResult(qres.pop());
			result.getElements().addAll(rightResult.getElements());
			
			envs.pop();
		}
		qres.push(result);
	}
	
	@Override
	public void visitEqualsExpression(IEqualsExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

		Boolean resBool = areEqualResults(leftResult, rightResult);
		ISimpleResult<?> result = createSimpleResult(resBool);
		qres.push(result);
	}

	@Override
	public void visitGreaterThanExpression(IGreaterThanExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

		Boolean resBool = null;
		
		if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
			resBool = ((Number)leftResult.getValue()).doubleValue() > ((Number)rightResult.getValue()).doubleValue();
		} else {
			if (leftResult == null || rightResult == null) {
				resBool = false;
			} else {
				int compRes = compareResults(leftResult, rightResult);
				resBool = compRes > 0;
			}
		}
		
		ISimpleResult<?> result = createSimpleResult(resBool);
		qres.push(result);
	}

	@Override
    public void visitGreaterOrEqualThanExpression(
        IGreaterOrEqualThanExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

		Boolean resBool = null;
		
		if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
			resBool = ((Number)leftResult.getValue()).doubleValue() >= ((Number)rightResult.getValue()).doubleValue();
		} else {
			int compRes = compareResults(leftResult, rightResult);
			resBool = compRes > 0;
		}
		
		ISimpleResult<?> result = createSimpleResult(resBool);
		qres.push(result);
    }

	@Override
    public void visitInExpression(IInExpression expr) {
	    expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        IBagResult rightResult = toBagResult(qres.pop());
        IBagResult leftResult = toBagResult(qres.pop());
        
        ArrayList<ISingleResult> refLeft = (ArrayList<ISingleResult>) leftResult.getElements();
        
        Boolean resBool = true;
        for (int i = 0; i < refLeft.size(); i++) {
            if (!rightResult.getElements().contains(refLeft.get(i))) {
                resBool = false;
                break;
            }
        }
        
        ISimpleResult<?> result = createSimpleResult(resBool);
        qres.push(result);    
    }

	@Override
    public void visitIntersectExpression(IIntersectExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		IBagResult rightResult = toBagResult(qres.pop());
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		Collection<ISingleResult> resCollection = new ArrayList<ISingleResult>();
		ArrayList<ISingleResult> refLeft = (ArrayList<ISingleResult>) leftResult.getElements();
		
		for (int i = 0; i < refLeft.size(); i++) {
			if (rightResult.getElements().contains((refLeft.get(i))))
				resCollection.add(refLeft.get(i));
		}
        
		result = new BagResult(resCollection);
		
		qres.push(result);
    }

	@Override
    public void visitJoinExpression(IJoinExpression expr) {
		expr.getLeftExpression().accept(this);
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		for (ISingleResult leftElement : leftResult.getElements()) {
			IENVSFrame frame = envs.nested(leftElement, store);
			envs.push(frame);
			
			expr.getRightExpression().accept(this);
			IBagResult rightResultBag = toBagResult(qres.pop());
			
			IBagResult leftElementBag = toBagResult(leftElement);
			IBagResult subResult = cartesianProduct(leftElementBag, rightResultBag);
			result.getElements().addAll(subResult.getElements());
			
			envs.pop();
		}
		qres.push(result);
    }

	@Override
    public void visitLessOrEqualThanExpression(
    		ILessOrEqualThanExpression expr) {
			expr.getLeftExpression().accept(this);
			expr.getRightExpression().accept(this);
			
			ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
			ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

			Boolean resBool = null;
			
			if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
				resBool = ((Number)leftResult.getValue()).doubleValue() <= ((Number)rightResult.getValue()).doubleValue();
			} else {
				int compRes = compareResults(leftResult, rightResult);
				resBool = compRes > 0;
			}
			
			ISimpleResult<?> result = createSimpleResult(resBool);
			qres.push(result);	    
    }

	@Override
    public void visitLessThanExpression(
    	ILessThanExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

		Boolean resBool = null;
		
		if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
			resBool = ((Number)leftResult.getValue()).doubleValue() < ((Number)rightResult.getValue()).doubleValue();
		} else {
			int compRes = compareResults(leftResult, rightResult);
			resBool = compRes > 0;
		}
		
		ISimpleResult<?> result = createSimpleResult(resBool);
		qres.push(result);	    
    }

	@Override
    public void visitMinusExpression(IMinusExpression expr) {
	    expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        
        ISimpleResult<?> result = null;
        
        if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
            Number resNum = substract((Number)leftResult.getValue(), (Number)rightResult.getValue());
            result = createSimpleResult(resNum);
        } else {
            throw new RuntimeException();
        }
        
        qres.push(result);
    }

	@Override
	public void visitMinusSetExpression(IMinusSetExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		IBagResult rightResult = toBagResult(qres.pop());
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		Collection<ISingleResult> resCollection = new ArrayList<ISingleResult>();
		ArrayList<ISingleResult> refLeft = (ArrayList<ISingleResult>) leftResult.getElements();
		ArrayList<ISingleResult> refRight = (ArrayList<ISingleResult>) rightResult.getElements();
		
		for (int i = 0; i < refLeft.size(); i++) {
			if (!refRight.contains(refLeft.get(i)))
				resCollection.add(refLeft.get(i));
		}
        
		result = new BagResult(resCollection);
		
		qres.push(result);
	}

	@Override
    public void visitModuloExpression(IModuloExpression expr) {
	    expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        
        ISimpleResult<?> result = null;
        
        if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
            Number resNum = modulo((Number)leftResult.getValue(), (Number)rightResult.getValue());
            result = createSimpleResult(resNum);
        } else {
            throw new RuntimeException();
        }
        
        qres.push(result);
    }

	@Override
    public void visitMultiplyExpression(IMultiplyExpression expr) {
	    expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        
        ISimpleResult<?> result = null;
        
        if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
            Number resNum = multiply((Number)leftResult.getValue(), (Number)rightResult.getValue());
            result = createSimpleResult(resNum);
        } else {
            throw new RuntimeException();
        }
        
        qres.push(result);
    }

	@Override
	public void visitNotEqualsExpression(INotEqualsExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);

		Boolean resBool = !areEqualResults(leftResult, rightResult);
		ISimpleResult<?> result = createSimpleResult(resBool);
		qres.push(result);
	}

    @Override
    public void visitOrderByExpression(IOrderByExpression expr) {
    }

    @Override
    public void visitOrExpression(IOrExpression expr) {
        expr.getLeftExpression().accept(this);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        expectBooleanResult(leftResult);
        
        Boolean resBool = (Boolean)leftResult.getValue();
        if (!resBool) {
            expr.getRightExpression().accept(this);
            ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
            expectBooleanResult(rightResult);
            
            resBool = (Boolean)rightResult.getValue();
        }
        
        qres.push(createSimpleResult(resBool));
    }

	@Override
	public void visitPlusExpression(IPlusExpression expr) {
		expr.getLeftExpression().accept(this);
		expr.getRightExpression().accept(this);
		
		ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
		ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
		
		ISimpleResult<?> result = null;
		if (leftResult instanceof IStringResult || rightResult instanceof IStringResult) {
			String left = leftResult.getValue().toString();
			String right = rightResult.getValue().toString();
			result = createSimpleResult(left.concat(right));
		} else if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
			Number resNum = sum((Number)leftResult.getValue(), (Number)rightResult.getValue());
			result = createSimpleResult(resNum);
		} else {
		    throw new RuntimeException();
		}

		qres.push(result);
	}

	@Override
    public void visitUnionExpression(IUnionExpression expr) {
        expr.getLeftExpression().accept(this);
        expr.getRightExpression().accept(this);
        
        IBagResult rightResult = toBagResult(qres.pop());
        IBagResult leftResult = toBagResult(qres.pop());
        
        IBagResult result = new BagResult(new ArrayList<ISingleResult>());
        Collection<ISingleResult> resCollection = (ArrayList<ISingleResult>) leftResult.getElements();
        ArrayList<ISingleResult> refRight = (ArrayList<ISingleResult>) rightResult.getElements();
        
        for (int i = 0; i < refRight.size(); i++) {
            resCollection.add(refRight.get(i));
        }
        
        result = new BagResult(resCollection);
        qres.push(result);    
    }

	@Override
	public void visitWhereExpression(IWhereExpression expr) {
		expr.getLeftExpression().accept(this);
		IBagResult leftResult = toBagResult(qres.pop());
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		for (ISingleResult leftElement : leftResult.getElements()) {
			IENVSFrame frame = envs.nested(leftElement, store);
			envs.push(frame);
			
			expr.getRightExpression().accept(this);
			ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
			expectBooleanResult(rightResult);
			
			if ((Boolean) rightResult.getValue()) {
				result.getElements().add(leftElement);
			}			
			envs.pop();
		}
		qres.push(result);
	}

	@Override
    public void visitXORExpression(IXORExpression expr) {
		expr.getLeftExpression().accept(this);
        ISimpleResult<?> leftResult = toSimpleResult(qres.pop(), store);
        expectBooleanResult(leftResult);
        
        expr.getRightExpression().accept(this);
        ISimpleResult<?> rightResult = toSimpleResult(qres.pop(), store);
        expectBooleanResult(rightResult);
        
        ISimpleResult<?> result = null;
        Boolean resBool = ((Boolean) leftResult.getValue() != (Boolean) rightResult.getValue());    
        result = createSimpleResult(resBool);
        qres.push(result);
    }

	@Override
	public void visitBooleanTerminal(IBooleanTerminal expr) {
		this.qres.push(createSimpleResult(expr.getValue()));
	}

	@Override
	public void visitDoubleTerminal(IDoubleTerminal expr) {
		this.qres.push(createSimpleResult(expr.getValue()));
	}

	@Override
	public void visitIntegerTerminal(IIntegerTerminal expr) {
		this.qres.push(createSimpleResult(expr.getValue()));
	}

	@Override
	public void visitNameTerminal(INameTerminal expr) {
		IBagResult evalRes = envs.bind(expr.getName());
		qres.push(evalRes);
	}

	@Override
	public void visitStringTerminal(IStringTerminal expr) {
		this.qres.push(createSimpleResult(expr.getValue()));
	}

	@Override
	public void visitBagExpression(IBagExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		
		IBagResult result;
		if (innerResult.getElements().size() == 1) {
			ISingleResult innerSingleResult = toSingleResult(innerResult);
			if (innerSingleResult instanceof IStructResult) {
				result = structToBagResult((IStructResult) innerSingleResult);
			} else {
				result = innerResult;
			}
		} else {
			result = innerResult;
		}
		qres.push(result);
	}

	@Override
    public void visitCountExpression(ICountExpression expr) {
	    expr.getInnerExpression().accept(this);
        IBagResult innerResult = toBagResult(qres.pop());
        
        int count = 0;
        for (int i = 0; i < innerResult.getElements().size(); i++) {
            count++;
        }
        ISimpleResult<?> result = createSimpleResult(count);
        qres.push(result);
    }

	@Override
	public void visitExistsExpression(IExistsExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		
		boolean exists = !innerResult.getElements().isEmpty();
		ISimpleResult<?> result = createSimpleResult(exists);
		qres.push(result);
	}

	@Override
	public void visitMaxExpression(IMaxExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());

		if (innerResult.getElements().isEmpty()) {
			throw new RuntimeException();
		}
		
		Number max = null;
		for (ISingleResult element : innerResult.getElements()) {
			ISimpleResult<?> simpleElement = toSimpleResult(element, store);
			expectNumberResult(simpleElement);
			Number n = (Number)simpleElement.getValue();
			max = max(max, n);
		}
		ISimpleResult<?> result = createSimpleResult(max);
		qres.push(result);
	}

	@Override
	public void visitMinExpression(IMinExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());

		if (innerResult.getElements().isEmpty()) {
			throw new RuntimeException();
		}
		
		Number min = null;
		for (ISingleResult element : innerResult.getElements()) {
			ISimpleResult<?> simpleElement = toSimpleResult(element, store);
			expectNumberResult(simpleElement);
			Number n = (Number)simpleElement.getValue();
			min = min(min, n);
		}
		ISimpleResult<?> result = createSimpleResult(min);
		qres.push(result);
	}

	@Override
    public void visitNotExpression(INotExpression expr) {
		expr.getInnerExpression().accept(this);
		ISimpleResult<?> innerResult = toSimpleResult(qres.pop(), store);
		
		expectBooleanResult(innerResult);
		boolean notRes = !(Boolean) innerResult.getValue();
		ISimpleResult<?> result = createSimpleResult(notRes);
		qres.push(result);
    }

	@Override
    public void visitStructExpression(IStructExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		
		IStructResult result = new StructResult(new ArrayList<ISingleResult>());
		for (ISingleResult element : innerResult.getElements()) {
			if (element instanceof IStructResult){
				result.elements().addAll(((IStructResult)element).elements());
			} else {
				result.elements().add((ISingleResult) element);
			}
		}
		qres.push(result);
    }

	@Override
	public void visitSumExpression(ISumExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		
		Number sum = null;
		
		for (ISingleResult element : innerResult.getElements()) {
			ISimpleResult<?> simpleElement = toSimpleResult(element, store);
			expectNumberResult(simpleElement);
			Number n = (Number)simpleElement.getValue();
			sum = sum(sum, n);
		}
		
		ISimpleResult<?> result = createSimpleResult(sum);
		qres.push(result);
	}

	@Override
    public void visitUniqueExpression(IUniqueExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());
		ArrayList<ISingleResult> innerRes = (ArrayList<ISingleResult>) innerResult.getElements();
		
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		
		for (int i = 0; i < innerRes.size(); i++) {
			if (!result.getElements().contains(innerRes.get(i))) {
				result.getElements().add(innerRes.get(i));
			}
		}
		
		qres.push(result);
    }

	@Override
	public void visitAvgExpression(IAvgExpression expr) {
		expr.getInnerExpression().accept(this);
		IBagResult innerResult = toBagResult(qres.pop());

		int bagSize = innerResult.getElements().size();
		if (bagSize == 0) {
			throw new RuntimeException();
		}
		
		Number sum = new Double(0);
		for (ISingleResult element : innerResult.getElements()) {
			ISimpleResult<?> simpleElement = toSimpleResult(element, store);
			expectNumberResult(simpleElement);
			Number n = (Number)simpleElement.getValue();
			sum = sum(sum, n);
		}
		double avg = sum.doubleValue() / bagSize;
		
		ISimpleResult<?> result = createSimpleResult(avg);
		qres.push(result);
	}
	
	@Override
    public void visitEmptyExpression(IEmptyExpression expr) {
	    expr.getInnerExpression().accept(this);
        IBagResult innerResult = toBagResult(qres.pop());
        
        Boolean resBool = null;
        
        if (innerResult.getElements().size() > 0)
        	resBool = false;
        else
        	resBool = true;
        
        ISimpleResult<?> result = createSimpleResult(resBool);
        qres.push(result);
    }

	@Override
	public IAbstractQueryResult eval(IExpression treeRoot) {
		try {
			init();
			treeRoot.accept(this);
			return qres.pop();
		} catch(RuntimeException e) {
			throw e;
		}
	}
	
	public void init() {
		qres = new QResStack();
		envs = new ENVS();
		envs.init(store.getEntryOID(), store);
	}
	
	public ISimpleResult<?> createSimpleResult(Object object) {
		if (object == null)
			return null;
		
		if (object instanceof String ) {
			return new StringResult((String)object);
		} else if (object instanceof Double) {
			return new DoubleResult((Double)object);
		} else if (object instanceof Integer) {
			return new IntegerResult((Integer)object);
		} else if (object instanceof Boolean) {
			return new BooleanResult((Boolean)object);
		} else {
			throw new RuntimeException();
		}
	}
	
	public ISimpleResult<?> createSimpleResult(ISimpleObject<?> object) {
		if (object == null)
			return null;
		return createSimpleResult(object.getValue());
	}
	
	public ISingleResult toSingleResult(IAbstractQueryResult res) {
		if (res == null)
			return null;

		if (res instanceof ICollectionResult)
			return toSingleResult((ICollectionResult) res);
		else if (res instanceof ISingleResult)
			return (ISingleResult) res;
		else{
			System.out.println("error");
			return null;
		}
	}

	public ISingleResult toSingleResult(ICollectionResult res) {
		if (res == null)
			return null;

		Collection<ISingleResult> elements;
		if (res instanceof IBagResult)
			elements = ((IBagResult) res).getElements();
		else if (res instanceof ISequenceResult)
			elements = ((ISequenceResult) res).getElements();
		else{
			System.out.println("error");
			return null;
		}
		return (ISingleResult) toSingleElement(elements);
	}
	
	public static IBagResult toBagResult(IAbstractQueryResult res) {
		if (res == null) {
			return new BagResult(new ArrayList<ISingleResult>());
		} else if (res instanceof ICollectionResult) {
			if (res instanceof IBagResult) {
				return (IBagResult) res;
			} else {
				throw new RuntimeException();
			}
		} else {
			IBagResult bag = new BagResult(new ArrayList<ISingleResult>());
			bag.getElements().add((ISingleResult) res);
			return bag;
		}
	}

	public ISimpleResult<?> toSimpleResult(IAbstractQueryResult res, ISBAStore store) {
		return toSimpleResult(toSingleResult(res), store);
	}

	public ISimpleResult<?> toSimpleResult(ISingleResult res, ISBAStore store) {
		if (res == null)
			return null;

		if (res instanceof IReferenceResult) {
			res = toSingleResult(resolveRefResult(res, store));
		}
		
		if (res instanceof IReferenceResult) {
			ISBAObject obj = store.retrieve(((IReferenceResult) res).getOIDValue());
			
			if (obj instanceof IStringObject)
				return new StringResult(((IStringObject) obj).getValue());
			else if (obj instanceof IDoubleObject)
				return new DoubleResult(((IDoubleObject) obj).getValue());
			else if (obj instanceof IIntegerObject)
				return new IntegerResult(((IIntegerObject) obj).getValue());
			else if (obj instanceof IBooleanObject)
				return new BooleanResult(((IBooleanObject) obj).getValue());
			else
				return null;
			
		} else if (res instanceof IBinderResult) {
			IBinderResult binder = ((IBinderResult) res);
			return toSimpleResult(binder.getValue(), store);
		} else if (res instanceof IStructResult) {
			return (ISimpleResult<?>) toSingleElement(((IStructResult) res).elements());
		} else if (res instanceof ISimpleResult<?>) {
			return (ISimpleResult<?>) res;
		} else {
			System.out.println("error");
			return null;
		}
	}
	
	public Object toSingleElement(Collection<?> res) {
		if (res == null || res.isEmpty())
			return null;
		else if (res.size() == 1)
			return res.iterator().next();
		else
			throw new IllegalArgumentException("Cannot change the collection of elements into single value.");
	}
	
	public IAbstractQueryResult resolveRefResult(IAbstractQueryResult res, ISBAStore store) {
		if (res instanceof IReferenceResult) {
			ISBAObject sbaObj = store.retrieve(((IReferenceResult)res).getOIDValue());
			if (!(sbaObj instanceof ISimpleObject)) {
				System.out.println("error");
				return null;
			}
		}
		return res;
	}
	
    public static boolean isNumberResult(ISimpleResult<?> res) {
        return (res != null) ? (res.getValue() instanceof Number) : false;
    }
    
    public static void expectNumberResult(ISimpleResult<?> result) {
        if (!isNumberResult(result)) {
            throw new RuntimeException();
        }
    }

    public static void expectNumberResults(ISimpleResult<?> leftResult, ISimpleResult<?> rightResult) {
        if (!isNumberResult(leftResult) || !isNumberResult(rightResult)) {
            throw new RuntimeException();
        }
    }

    public static boolean isBooleanResult(ISimpleResult<?> res) {
        return (res != null) ? (res.getValue() instanceof Boolean) : false;
    }
    
    public static void expectBooleanResult(ISimpleResult<?> result) {
        if (!(result instanceof IBooleanResult)) {
            throw new RuntimeException();
        }
    }
    
    public static void expectBooleanResults(ISimpleResult<?> leftResult, ISimpleResult<?> rightResult) {
        if (!(leftResult instanceof IBooleanResult) || !(rightResult instanceof IBooleanResult)) {
            throw new RuntimeException();
        }
    }
    
	public static Number castToProperNumberType(Double value, Number arg1, Number arg2) {
		Number res = null;
		if(arg1 instanceof Double || arg2 instanceof Double) {
			res = value;
		} else if(arg1 instanceof Integer || arg2 instanceof Integer) {
			res = value.intValue();
		} else {
			throw new RuntimeException();
		}
		return res;
	}
	
	public static IBagResult structToBagResult(IStructResult struct) {
		IBagResult result = new BagResult(new ArrayList<ISingleResult>());
		result.getElements().addAll(struct.elements());
		return result;
	}
	
	public static IBagResult cartesianProduct(IBagResult leftResult, IBagResult rightResult) {
		IBagResult res = new BagResult(new ArrayList<ISingleResult>());
		
		for (ISingleResult leftElement : leftResult.getElements()) {
			for (ISingleResult rightElement : rightResult.getElements()) {
				IStructResult struct = new StructResult(new ArrayList<ISingleResult>());
				
				if (leftElement instanceof IStructResult) {
					struct.elements().addAll(((IStructResult) leftElement).elements());
				} else {
					struct.elements().add(leftElement);
				}
				
				if (rightElement instanceof IStructResult) {
					struct.elements().addAll(((IStructResult) rightElement).elements());
				} else {
					struct.elements().add(rightElement);
				}
				
				res.getElements().add(struct);
			}
		}
		return res;
	}
	
	public static int compareResults(ISimpleResult<?> leftResult, ISimpleResult<?> rightResult) {
		return compareValues((Comparable<?>)leftResult.getValue(), (Comparable<?>)rightResult.getValue());
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static int compareValues(Comparable arg1, Comparable arg2) {
		boolean c1Null = (arg1 == null);
		boolean c2Null = (arg2 == null);
		
		if(c1Null && c2Null) {
			return 0;
		} else if(c1Null && !c2Null) {
			return -1;
		} else if(!c1Null && c2Null) {
			return 1;
		} else {
			return arg1.compareTo(arg2);
		}
	}
	
	public static boolean areEqualResults(ISimpleResult<?> leftResult, ISimpleResult<?> rightResult) {
		if (isNumberResult(leftResult) && isNumberResult(rightResult)) {
			return ((Number)leftResult.getValue()).doubleValue() == ((Number)rightResult.getValue()).doubleValue();
		} else {
		    if (isBooleanResult(leftResult) && isBooleanResult(rightResult)) {
		        return ((Boolean)leftResult.getValue()).booleanValue() == ((Boolean)rightResult.getValue()).booleanValue();
		    } else {
		        if(leftResult == null || rightResult == null) {
		        	return false;
		        }
			}
			return leftResult.equals(rightResult);
		}
	}
	
	public static Number divide(Number arg1, Number arg2) {
		Number res = null;
		Double sum = arg1.doubleValue() / arg2.doubleValue();
		res = castToProperNumberType(sum, arg1, arg2);
		return res;
	}
	
	public static Number max(Number arg1, Number arg2) {
        if(arg1 == null) {
            return arg2;
        }
        if(arg2 == null) {
            return arg1;
        }
		Number res = null;
		Double max = Math.max(arg1.doubleValue(), arg2.doubleValue());
		res = castToProperNumberType(max, arg1, arg2);
		return res;
	}
	
	public static Number min(Number arg1, Number arg2) {
        if(arg1 == null) {
            return arg2;
        }
        if(arg2 == null) {
            return arg1;
        }
		Number res = null;
		Double min = Math.min(arg1.doubleValue(), arg2.doubleValue());
		res = castToProperNumberType(min, arg1, arg2);
		return res;
	}
	
	public static Number modulo(Number arg1, Number arg2) {
		Number res = null;
		Double sum = arg1.doubleValue() % arg2.doubleValue();
		res = castToProperNumberType(sum, arg1, arg2);
		return res;
	}
	
	public static Number multiply(Number arg1, Number arg2) {
		Number res = null;
		Double sum = arg1.doubleValue() * arg2.doubleValue();
		res = castToProperNumberType(sum, arg1, arg2);
		return res;
	}
	
	public static Number substract(Number arg1, Number arg2) {
		Number res = null;
		Double sum = arg1.doubleValue() - arg2.doubleValue();
		res = castToProperNumberType(sum, arg1, arg2);
		return res;
	}
	
	public static Number sum(Number arg1, Number arg2) {
        if(arg1 == null) {
            return arg2;
        }
        if(arg2 == null) {
            return arg1;
        }
		Number res = null;
		Double sum = arg1.doubleValue() + arg2.doubleValue();
		res = castToProperNumberType(sum, arg1, arg2);
		return res;
	}
}